home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue62 / Alfresco / AAHpDbug.pas next >
Encoding:
Pascal/Delphi Source File  |  2000-09-02  |  12.3 KB  |  411 lines

  1. {*********************************************************}
  2. {* AAHpDbug                                              *}
  3. {* Copyright (c) Julian M Bucknall 2000                  *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Algorithms Alfresco: Heap debugger                    *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit AAHpDbug;
  14.  
  15. {WARNING: this unit *must* appear first in your project's uses list.}
  16.  
  17. interface
  18.  
  19. implementation
  20.  
  21. uses
  22.   Windows, // it's OK to use the Windows unit: it allocates no memory
  23.   AANoMem; // this unit implements routines that use no heap memory
  24.  
  25. type
  26.   PPointerArray = ^TPointerArray;
  27.   TPointerArray =
  28.      array [0..pred(MaxInt div sizeof(pointer))] of pointer;
  29.  
  30. var
  31.   OrigHeap : TMemoryManager;
  32.   OurHeap  : TMemoryManager;
  33.   LogName  : shortstring;
  34.   GuardSize: integer;
  35.   {list of currently allocated blocks}
  36.   PtrArray : PPointerArray;
  37.   PASize   : integer;
  38.   PACount  : integer;
  39.   {delay queue variables}
  40.   DelaySize  : integer;
  41.   DelayQueue : PPointerArray;
  42.   QHead      : integer;
  43.   QTail      : integer;
  44.  
  45.  
  46. {===Sorted array of currently allocated blocks=======================}
  47. procedure AddPointer(P : pointer);
  48. var
  49.   L, R, M : integer;
  50. begin
  51.   if (PACount = PASize) then begin
  52.     inc(PASize, 1024);
  53.     HeapRealloc(GetProcessHeap, 0, PtrArray, PASize * sizeof(pointer));
  54.   end;
  55.   if (PACount = 0) then begin
  56.     PtrArray[0] := P;
  57.     PACount := 1;
  58.   end
  59.   else begin
  60.     L := 0;
  61.     R := pred(PACount);
  62.     while (L <= R) do begin
  63.       M := (L + R) div 2;
  64.       if (longint(P) < longint(PtrArray[M])) then
  65.         R := M - 1
  66.       else
  67.         L := M + 1;
  68.     end;
  69.     if (L < PACount) then
  70.       Move(PtrArray[L], PtrArray[L+1], (PACount-L)*sizeof(pointer));
  71.     PtrArray[L] := P;
  72.     inc(PACount);
  73.   end;
  74. end;
  75. {--------}
  76. function RemovePointer(P : pointer) : boolean;
  77. var
  78.   L, R, M : integer;
  79. begin
  80.   if (PACount = 0) then
  81.     Result := false
  82.   else begin
  83.     L := 0;
  84.     R := pred(PACount);
  85.     while L <= R do begin
  86.       M := (L + R) div 2;
  87.       if (longint(P) < longint(PtrArray[M])) then
  88.         R := M - 1
  89.       else if (longint(P) > longint(PtrArray[M])) then
  90.         L := M + 1
  91.       else begin
  92.         dec(PACount);
  93.         if (M <> PACount) then
  94.           Move(PtrArray[M+1], PtrArray[M], (PACount-M)*sizeof(pointer));
  95.         Result := true;
  96.         Exit;
  97.       end;
  98.     end;
  99.     Result := false;
  100.   end;
  101. end;
  102. {====================================================================}
  103.  
  104.  
  105. {===Logging stuff====================================================}
  106. function HeapErrorMsg(aErrorCode : integer) : shortstring;
  107. begin
  108.   case aErrorCode of
  109.      1 : Result := 'operating system returned an error on release';
  110.      2 : Result := 'operating system returned an error on decommit';
  111.      3 : Result := 'list of committed blocks looks bad';
  112.      4,
  113.      5,
  114.      6 : Result := 'filler block is bad';
  115.      7 : Result := 'current allocation zone is bad';
  116.      8 : Result := 'couldn''t initialize';
  117.      9 : Result := 'used block looks bad (invalid pointer? double free?)';
  118.     10 : Result := 'prev block before a used block is bad';
  119.     11 : Result := 'next block after a used block is bad';
  120.     12 : Result := 'free list is bad';
  121.     13 : Result := 'free block is bad';
  122.     14 : Result := 'free list doesn''t correspond to blocks marked free';
  123.     99 : Result := 'invalid pointer: not allocated with GetMem, or already freed';
  124.   else
  125.     Result := 'unknown error message';
  126.   end;
  127. end;
  128. {--------}
  129. procedure WriteLogFreeErr(const aMsg : shortstring);
  130. var
  131.   Log : System.text;
  132. begin
  133.   aaLogOpen(Log, LogName);
  134.   try
  135.     writeln(Log, 'FreeMem error: ', aMsg);
  136.   finally
  137.     aaLogClose(Log);
  138.   end;
  139. end;
  140. {--------}
  141. procedure WriteLogOverwrite(P : pointer; aWhere : integer;
  142.                             aMemory : pointer; aLen : integer);
  143. var
  144.   PAsStr : array [0..9] of char;
  145.   Log    : System.Text;
  146. begin
  147.   aaLogOpen(Log, LogName);
  148.   try
  149.     write(Log, 'Memory overwrite detected with block: ');
  150.     aaPointerAsHexZ(PAsStr, P);
  151.     writeln(Log, PAsStr);
  152.     case aWhere of
  153.       0 : writeln(Log, '...overwrite occurred after the block');
  154.       1 : writeln(Log, '...overwrite occurred before the block');
  155.       2 : writeln(Log, '...overwrite occurred within the block after freeing');
  156.     end;
  157.     if (aWhere = 2) then begin
  158.       writeln(Log, '...memory block contents (', aLen, ' bytes):');
  159.       aaLogWriteBuffer(Log, aMemory, aLen);
  160.     end
  161.     else begin
  162.       writeln(Log, '...guard block contents (', aLen, ' bytes):');
  163.       aaLogWriteBuffer(Log, aMemory, aLen);
  164.     end;
  165.   finally
  166.     aaLogClose(Log);
  167.   end;
  168. end;
  169. {--------}
  170. procedure WriteLogLeaks;
  171. var
  172.   Log : System.text;
  173.   i   : integer;
  174.   P   : pointer;
  175.   Size: integer;
  176. begin
  177.   aaLogOpen(Log, LogName);
  178.   try
  179.     writeln(Log, 'Memory leaks: ', PACount);
  180.     for i := 0 to pred(PACount) do begin
  181.       P := PtrArray[i];
  182.       Size := PInteger(PChar(P) - GuardSize - sizeof(integer))^;
  183.       writeln(Log, '...leaked block ', i, ' contents (', Size, ' bytes):');
  184.       aaLogWriteBuffer(Log, P, Size);
  185.     end;
  186.   finally
  187.     aaLogClose(Log);
  188.   end;
  189. end;
  190. {====================================================================}
  191.  
  192.  
  193. {===Guard block code=================================================}
  194. procedure CheckGuardBlocks(P : pointer);
  195. var
  196.   Mem  : PChar;
  197.   Size : integer;
  198.   RoundedSize : integer;
  199.   SecondSize  : integer;
  200. begin
  201.   {get the address of the first guard block, and verify that it hasn't
  202.    been changed by an overwrite}
  203.   Mem := P;
  204.   dec(Mem, GuardSize);
  205.   if not aaCompareMem(Mem, GuardSize, $CC) then
  206.     WriteLogOverwrite(P, 1, Mem, GuardSize);
  207.   {get the size of the user's memory block, and work out the address
  208.    and size of the second guard block; verify that it also hasn't been
  209.    changed}
  210.   dec(Mem, sizeof(integer));
  211.   Size := PInteger(Mem)^;
  212.   inc(Mem, sizeof(integer) + GuardSize + Size);
  213.   RoundedSize := (Size + 3) and $7FFFFFFC;
  214.   SecondSize := GuardSize + (RoundedSize - Size);
  215.   if not aaCompareMem(Mem, SecondSize, $CC) then
  216.     WriteLogOverwrite(P, 0, Mem, GuardSize);
  217. end;
  218. {====================================================================}
  219.  
  220.  
  221. {===Replacement memory routines======================================}
  222. function OurGetMem(Size : integer) : pointer;
  223. type
  224.   PInteger = ^integer;
  225. var
  226.   RoundedSize : integer;
  227. begin
  228.   {on a GetMem, we have to add the size of our guard blocks and an
  229.    extra size value to the size to allocate; round up to nearest 4
  230.    bytes}
  231.   RoundedSize := (Size + (2 * GuardSize) + sizeof(integer) + 3) and
  232.                  $7FFFFFFC;
  233.   {get the memory}
  234.   Result := OrigHeap.GetMem(RoundedSize);
  235.   {providing some memory was allocated...}
  236.   if (Result <> nil) then begin
  237.     {save the original size at the start of the block}
  238.     PInteger(Result)^ := Size;
  239.     {advance the result pointer over this size value}
  240.     inc(PChar(Result), sizeof(integer));
  241.     {fill remainder of memory block with $CC}
  242.     FillChar(Result^, RoundedSize - sizeof(integer), $CC);
  243.     {return the address of the memory block in between the two guard
  244.      blocks}
  245.     inc(PChar(Result), GuardSize);
  246.     {add it to our list of allocated blocks}
  247.     AddPointer(Result);
  248.   end;
  249. end;
  250. {--------}
  251. function OurFreeMem(P : pointer) : integer;
  252. var
  253.   BlockSize : integer;
  254. begin
  255.   {the system unit's FreeMem routine will not pass a nil pointer to
  256.    this routine, only our own clean-up routine}
  257.  
  258.   {check to see if the pointer exists in our list}
  259.   if (P <> nil) and (not RemovePointer(P)) then begin
  260.     {it's not a valid pointer}
  261.     WriteLogFreeErr(HeapErrorMsg(99));
  262.     Result := 99;
  263.   end
  264.   else begin
  265.     {add the pointer to the delay queue}
  266.     if (P <> nil) then begin
  267.       BlockSize := PInteger(PChar(P) - GuardSize - sizeof(integer))^;
  268.       FillChar(P^, BlockSize, $CC);
  269.     end;
  270.     DelayQueue[QTail] := P;
  271.     QTail := (QTail + 1) mod DelaySize;
  272.     {check to see whether we can actually free a pointer}
  273.     if (QHead <> QTail) then
  274.       Result := 0
  275.     else begin
  276.       {get the pointer at the head of the queue}
  277.       P := DelayQueue[QHead];
  278.       QHead := (QHead + 1) mod DelaySize;
  279.       {check that the user hasn't overwritten the guard bytes}
  280.       CheckGuardBlocks(P);
  281.       {check that the memory block itself wasn't overwritten}
  282.       BlockSize := PInteger(PChar(P) - GuardSize - sizeof(integer))^;
  283.       if not aaCompareMem(P, BlockSize, $CC) then
  284.         WriteLogOverwrite(P, 2, P, BlockSize);
  285.       {move to the size value stored in the block: it is this pointer
  286.        pointer that will get freed}
  287.       dec(PChar(P), GuardSize + sizeof(integer));
  288.       {free the memory}
  289.       Result := OrigHeap.FreeMem(P);
  290.       if (Result <> 0) then
  291.         WriteLogFreeErr(HeapErrorMsg(Result));
  292.     end;
  293.   end;
  294. end;
  295. {--------}
  296. function OurReallocMem(P : pointer; Size : integer) : pointer;
  297. var
  298.   OrigSize : integer;
  299. begin
  300.   {Note: ReallocMem has to be done with a GetMem and a FreeMem, so
  301.    that we can maintain control of the whole process}
  302.  
  303.   {if P is nil, we merely get a new allocation if Size is non-zero}
  304.   if (P = nil) then begin
  305.     if (Size <= 0) then
  306.       Result := nil
  307.     else
  308.       Result := OurGetMem(Size);
  309.   end
  310.   {otherwise, if Size is zero we free P, or if Size is non-zero we
  311.    allocate another block, copy the contents of the current block over
  312.    and then free the current block}
  313.   else begin
  314.     if (Size = 0) then
  315.       Result := nil
  316.     else begin
  317.       Result := OurGetMem(Size);
  318.       OrigSize := PInteger(PChar(P) - GuardSize - sizeof(integer))^;
  319.       if (OrigSize < Size) then
  320.         Move(P^, Result^, OrigSize)
  321.       else
  322.         Move(P^, Result^, Size);
  323.     end;
  324.     OurFreeMem(P);
  325.   end;
  326. end;
  327. {====================================================================}
  328.  
  329.  
  330. {===Initialization/finalization======================================}
  331. procedure InitializeUnit;
  332. var
  333.   LogNameZ : array [0..255] of char;
  334. begin
  335.   {get the guard block size (must be multiple of 4 between 4 and 32)}
  336.   GuardSize := aaReadRegistryInt('software\AlgorithmsAlfresco\AAHpDbug',
  337.                                  'GuardSize',
  338.                                  16);
  339.   if (GuardSize < 4) then
  340.     GuardSize := 4
  341.   else if (GuardSize > 32) then
  342.     GuardSize := 32
  343.   else
  344.     GuardSize := (GuardSize + 3) and $7FFFFFFC;
  345.  
  346.   {get the delay queue size (must be between 20 and 100)}
  347.   DelaySize := aaReadRegistryInt('software\AlgorithmsAlfresco\AAHpDbug',
  348.                                  'DelaySize',
  349.                                  32);
  350.   if (DelaySize < 20) then
  351.     DelaySize := 20
  352.   else if (DelaySize > 1000) then
  353.     DelaySize := 100;
  354.  
  355.   {get the log name}
  356.   aaReadRegistryString(LogNameZ, 256,
  357.                        'software\AlgorithmsAlfresco\AAHpDbug',
  358.                        'LogName',
  359.                        'C:\HEAPDBUG.LOG');
  360.   LogName := aaStrPas(LogNameZ);
  361.  
  362.   {create an array of pointers to hold the valid memory blocks}
  363.   PtrArray := HeapAlloc(GetProcessHeap, 0, 1024 * sizeof(pointer));
  364.   PASize := 1024;
  365.   PACount := 0;
  366.  
  367.   {create an array to hold the delay queue for freed blocks that
  368.    haven't been freed yet}
  369.   DelayQueue := HeapAlloc(GetProcessHeap, 0, DelaySize * sizeof(pointer));
  370.   QHead := 0;
  371.   QTail := 0;
  372.  
  373.   {get the original manager}
  374.   GetMemoryManager(OrigHeap);
  375.  
  376.   {set up our heap manager}
  377.   OurHeap.GetMem := OurGetMem;
  378.   OurHeap.FreeMem := OurFreeMem;
  379.   OurHeap.ReallocMem := OurReallocMem;
  380.  
  381.   {replace heap manager with ours}
  382.   SetMemoryManager(OurHeap);
  383. end;
  384. {--------}
  385. procedure FinalizeUnit;
  386. begin
  387.   {free the remaining pointers in the delay queue}
  388.   while (DelayQueue[QHead] <> nil) do
  389.     OurFreeMem(nil);
  390.   {report the memory leaks}
  391.   if (PACount <> 0) then
  392.     WriteLogLeaks;
  393.  
  394.   {restore the original manager}
  395.   SetMemoryManager(OrigHeap);
  396.  
  397.   {free our array of pointers}
  398.   HeapFree(GetProcessHeap, 0, PtrArray);
  399.   {free the delay queue}
  400.   HeapFree(GetProcessHeap, 0, DelayQueue);
  401. end;
  402. {====================================================================}
  403.  
  404. initialization
  405.   InitializeUnit;
  406.  
  407. finalization
  408.   FinalizeUnit;
  409.  
  410. end.
  411.